<?php
/**
 * Created by PhpStorm.
 * User: Qch
 * Date: 2017/3/24
 * Time: 21:23
 */

namespace J\Util\Platform;


use J\Exception;
use J\Message;
use J\Schema\Syntax;
use J\Schema\Sprite;
use J\Util\Database;

/**
 * 自动组装
 *   查询表达式()
 *   计算表达式()
 * 生成DSN参数
 *
 *
 */
class Builder
{
    /**
     * @var Builder[]  平台实例
     */
    static protected  $instance   =  array();

    /**
     * @var Database
     */
    protected $eloquent;

    /**
     * 生成 查询表达式
     *
     * @param array $conditions
     * @return array
     * @throws Exception
     */
    public function mapCondition($conditions)
    {
        $list = '';
        $op = '(';
        $binding = [];

        foreach ($conditions as $or) {
            $list .= $op . $this->makeCondition($or, $binding).')';
            $op = ' OR (';
        }
        return [$list, $binding];
    }

    /**
     * 生成比较项
     * @param $conditions
     * @param $binding
     * @return string
     * @throws Exception
     */
    protected function makeCondition($conditions, &$binding)
    {
        $list = '';
        $op = '';
        if(!is_array($conditions)) return '0';

        foreach ($conditions as $and) {
            $list .= $op . $this->mapCompare($and, $binding);
            $op = ' AND ';
        }
        return $list;
    }

    /**
     * 生成 比较表达式
     * @param Sprite $value
     * @param array $binding
     * @return string
     * @throws Exception
     */
    protected function mapCompare($value, &$binding)
    {
        if ($value === true) return 1;
        if ($value === false) return 0;

        switch ($value->getOperator()) {
            case Syntax::OPERATOR_EXPRESSION :
                $list = $value->getValue();
                break;
            case Syntax::OPERATOR_IN :
                $list = $this->createIn($value->getColumn(), $value->getValue(), $value->getBinding());
                break;
            case Syntax::OPERATOR_NOT_IN :
                $list = $this->createNotIn($value->getColumn(), $value->getValue(), $value->getBinding());
                break;
            case Syntax::OPERATOR_IS_NULL :
                $list = $value->getColumn() . 'IS NULL';
                break;
            case Syntax::OPERATOR_NOT_NULL :
                $list = $value->getColumn() . 'IS NOT NULL';
                break;
            case Syntax::OPERATOR_BETWEEN :
                $list = $this->between($value);
                break;
            case Syntax::OPERATOR_LIKE :
                $list = $value->getColumn() . ' LIKE ' . $this->quote('%' .$value->getValue() . '%');
                break;
            case Syntax::OPERATOR_NOT_LIKE :
                $list = $value->getColumn() . ' NOT LIKE ' . $this->quote('%' . $value->getValue() . '%');
                break;
            default:
                $name = ':' . $value->getColumn() . count($binding);
                $list = $value->getColumn() . $value->getOperator() . $name;
                $binding[$name] = $value;
                break;
        }
        return $list;
    }

    public function createIn($field, $values, $type = \PDO::PARAM_INT)
    {
        return $this->createNotIn($field, $values, $type, 'IN');
    }

    protected function createNotIn($field, $values, $type = \PDO::PARAM_INT, $op = 'NOT IN')
    {
        if(empty($values) or !is_array($values)) return 0;

        $ids = array_map(
            function ($n) use ($type){
                if($type === \PDO::PARAM_STR){
                    return $this->quote($n);
                }else{
                    return (int)$n;
                }
            }, $values
        );
        return $field . ' ' . $op . '(' . implode(',', $ids) . ') ';
    }

    /**
     * @param Sprite $value
     * @return string
     */
    protected function between(Sprite $value)
    {
        $v = $value->getValue();
        return $value->getColumn() . ' BETWEEN ' . $v[0] . ' AND ' . $v[1];
    }

    /**
     * 生成 计算表达式
     * @param Sprite[] $data
     * @param bool $forUpdate
     * @return array
     * @throws Exception
     */
    public function mapCalculator($data, $forUpdate = true)
    {
        $list = [];
        $bind = [];
        $fields = [];
        $values = [];
        foreach ($data as $value) {
            // 跳过置空项
            if (!$value) continue;

            switch ($value->getOperator()) {
                case Syntax::OPERATOR_SET_NULL:
                    $list[] = $value->getColumn() . '= NULL';
                    break;
                case '!':
                    $list[] = $value->getColumn() . '=!' . $value->getColumn();
                    break;
                case '++' :
                    $list[] = $value->getColumn() . '=' . $value->getColumn() . '+1';
                    break;
                case '--' :
                    $list[] = $value->getColumn() . '=' . $value->getColumn() . '-1';
                    break;
                case Syntax::OPERATOR_RAND:
                    $fields[] = $value->getColumn();
                    $values[] = 'RAND()';
                    $list[] = $value->getColumn() . '=' . 'RAND()';
                    break;
                case Syntax::OPERATOR_NOW:
                    $fields[] = $value->getColumn();
                    $values[] = time();
                    $list[] = $value->getColumn() . '=' . time();
                    break;
                case '.':
                    $list[] = $this->concat($value->getColumn(), $value->getValue());
                    break;
                case Syntax::OPERATOR_REPLACE:
                    $list[] = $this->strReplace($value->getColumn(), $value->getValue());
                    break;
                default:
                    $fields[] = $value->getColumn();
                    $values[] = $binding_field = ':' . $value->getColumn();
                    if ($value->getOperator() === '=') {
                        $list[] = $value->getColumn() . '=' . $binding_field;
                    } else {
                        $list[] = $value->getColumn() . '=' . $value->getColumn() . $value->getOperator() . $binding_field;
                    }

                    $bind[$binding_field] = $value;
                    break;
            }
        }
        if ($forUpdate) {
            return [implode(',', $list), $bind];
        } else {
            return [[implode(',', $fields), implode(',', $values)], $bind];
        }

    }

    /**
     * concat语句
     * @param $field
     * @param $value
     * @return string
     * @throws Exception
     */
    protected function concat($field, $value)
    {
        if (is_array($value)) {
            $prefix = $this->quote($value[0]);
            $suffix = $this->quote($value[1]);
        } else {
            $prefix = '';
            $suffix = $this->quote($value);
        }
        return $field . '=CONCAT(' . $prefix . ',' . $field . ',' . $suffix . ')';
    }

    /**
     * Replace语句
     * @param $field
     * @param $value
     * @return string
     * @throws Exception
     */
    protected function strReplace($field, $value)
    {
        if (is_array($value)) {
            $from = $this->quote($value[0]);
            $to = $this->quote($value[1]);
        } else {
            $from = $this->quote($value);
            $to = '';
        }
        return $field . '=REPLACE(' . $field . ',' . $from . ',' . $to . ')';
    }

    /**
     * 生成 高级查询扩展
     * @param array $options
     * @return array
     */
    public function mapOption($options = [])
    {
        $group = isset($options['group']) ? ' GROUP BY ' . $options['group'] : '';
        $limit = isset($options['limit']) ? $this->writeLimit($options['limit']) : '';
        $order = isset($options['order']) ? $this->writeOrder($options['order']) : '';

        return [$group, $order, $limit];
    }

    protected function writeLimit($limit)
    {
        return (' LIMIT ' . (is_array($limit) ? (implode(',', $limit)) : $limit));
    }

    protected function writeOrder($order)
    {
        $r = [];
        foreach ($order as $f => $o) {
            $r[] = $f . ' ' . ($o?'ASC ':'DESC ');
        }
        return ' ORDER BY '.implode(',', $r);
    }

    /**
     * @param $value
     * @return string
     * @throws Exception
     */
    protected function quote($value)
    {
        return $this->eloquent->getPdo()->quote($value);
    }

    /**
     * 目前是MYSQL的DSN，后续会增加其他格式的解析
     *
     * @param array $params
     *
     * @return string The DSN.
     */
    public function buildDsn(array $params)
    {
        $dsn = $params['type'].':';
        $setting = [
            'host','port','unix_socket','charset'
        ];
        foreach ($setting as $key) {
            if (isset($params[$key]) && !empty($params[$key])) {
                $dsn .= "$key={$params[$key]};";
            }
        }
        $dsn .= "dbname={$params['name']};";

        return $dsn;
    }

    /**
     * 获取构造器实例
     * @param $type
     * @param $eloquent
     * @return Builder
     * @throws Exception
     */
    public static function getInstance($type, $eloquent)
    {
        if(!isset(self::$instance[$type])){
            $class  = __NAMESPACE__.'\\'.ucfirst($type);

            if(class_exists($class)){
                /** @var Builder $obj */
                $obj = new $class();
                $obj->eloquent = $eloquent;
                self::$instance[$type] = $obj;
            }else{
                // 类没有定义
                throw new Exception(Message::NO_DB_DRIVER,['class'=>$class]);
            }
        }
        return self::$instance[$type];
    }
}